#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <memory.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

#ifdef __MINGW32__
#include <winsock.h>
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
struct ip_mreq 
{ 
    struct in_addr imr_multiaddr; 
    struct in_addr imr_interface;
};
#endif

#include "httpmu.h"
   
int httpmu_set_option(http_head_t *head, const char *key, const char *val)
{
    int flag = -1;
    for (int i = 0; i < MAX_HEAD_ITEM; i++) {
        if (head->options[i].key == NULL) {
            int len = strlen(key) + 1;
            head->options[i].key = (char *)httpmu_malloc(len);
            if (head->options[i].key == NULL) {
                return -1;
            }
            memcpy(head->options[i].key, key, len);
            len = strlen(val) + 1;
            head->options[i].value = (char *)httpmu_malloc(len);
            if (head->options[i].value == NULL) {
                httpmu_free(head->options[i].key);
                head->options[i].key = NULL;
                return -1;
            }
            memcpy(head->options[i].value, val, len);
            flag = 0;
            break;
        }
    }
    return flag;
}

const char *httpmu_get_option(http_head_t *head, const char *key)
{
    for (int i = 0; i < MAX_HEAD_ITEM; i++) {
        if (strcasecmp(head->options[i].key, key) == 0) {
            return head->options[i].value;
        }
    }
    return NULL;
}

http_head_t *httpmu_create_head(const char *method, const char *url)
{
    http_head_t *head = httpmu_malloc(sizeof(http_head_t));
    if (head == NULL)
        return NULL;
    memset(head, 0, sizeof(http_head_t));
    int len = strlen(method) + 1;
    head->method = httpmu_malloc(len);
    if (head->method == NULL) {
        httpmu_free(head);
        return NULL;
    }
    memcpy(head->method, method, len);
    
    len = strlen(url) + 1;
    head->url = httpmu_malloc(len);
    if (head->url == NULL) {
        httpmu_free(head->method);
        httpmu_free(head);
        return NULL;
    }
    memcpy(head->url, url, len);
    return head;
}

void httpmu_free_head(http_head_t *head)
{
    if (head->method) {
        httpmu_free(head->method);
        head->method = NULL;
    }
    if (head->url) {
        httpmu_free(head->url);
        head->url = NULL;
    }
    for (int i = 0; i < MAX_HEAD_ITEM; i++) {
        if (head->options[i].key) {
            httpmu_free(head->options[i].key);
            head->options[i].key = NULL;
        }
        if (head->options[i].value) {
            httpmu_free(head->options[i].value);
            head->options[i].value = NULL;
        }
    }
    httpmu_free(head);
}

static int httpmu_create_socket(const char *group_ip, uint16_t port)
{
	int err = 0;
	int sock = -1;
#ifdef __MINGW32__
    WSADATA Ws;
    WSAStartup(MAKEWORD(2, 2), &Ws);
#endif
	if((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
		printf("ERROR: socket - AF_INET, SOCK_DGRAM\n");
        return -1;
	}

	// Add multicast group membership on this interface
    struct ip_mreq imr;
    imr.imr_multiaddr.s_addr = inet_addr(group_ip);
    imr.imr_interface.s_addr = INADDR_ANY;
	if(setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr)) < 0) {
        printf("ERROR: setsockopt - IP_ADD_MEMBERSHIP\n");
		close(sock);
        return -1;
	}

	// Specify outgoing interface too
    struct in_addr intfAddr;
    intfAddr.s_addr = INADDR_ANY;
	if(setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &intfAddr, sizeof(struct in_addr)) < 0) {
		printf("ERROR: setsockopt - IP_MULTICAST_IF\n");
		close(sock);
        return -1;
	}

	// And start listening for packets
	if(port) {
		struct sockaddr_in bindAddr;
		bindAddr.sin_family = AF_INET;
		bindAddr.sin_port = htons(port);
		bindAddr.sin_addr.s_addr = INADDR_ANY;
		if(bind(sock, (struct sockaddr *) &bindAddr, sizeof(bindAddr)) < 0) {
            printf("ERROR: bind\n");
            close(sock);
            return -1;
        }
	}
    return sock;
}

static int httpmu_close_socket(int sock)
{
    close(sock);
#ifdef __MINGW32__
    WSACleanup();
#endif  
    return 0;
}

httpmu_c_t *httpmu_create_client(const char *group_ip, uint16_t port)
{
    httpmu_c_t *client = (httpmu_c_t *)httpmu_malloc(sizeof(httpmu_c_t));
    if (client == NULL)
        return NULL;
    client->socket = httpmu_create_socket(group_ip, port);
    if (client->socket < 0) {
        httpmu_free(client);
        return NULL;
    }
    return client;
}

void httpmu_free_client(httpmu_c_t *client)
{
    httpmu_close_socket(client->socket);
    httpmu_free(client);
}

int httpmu_client_send_request(httpmu_c_t *client, http_head_t *head, const char *content)
{
    int len = 0;
    char buff[1500] = {0};
    len = sprintf(buff, "%s %s HTTP/1.1\r\n", head->method, head->url);
    for (int i = 0; i < MAX_HEAD_ITEM; i++) {
        if (head->options[i].key == NULL)
            break;
        len += sprintf(buff + len, "%s: %s\r\n", head->options[i].key, head->options[i].value);
    }
    len += sprintf(buff + len, "\r\n");
    if (content) {
        len += sprintf(buff + len, "%s", content);
    }
    int socket = client->socket;
    struct sockaddr to;
    struct sockaddr_in *to_sin = (struct sockaddr_in*) &to;
    to_sin->sin_family = AF_INET;
    // parse dest host ip, port
    const char *dst = httpmu_get_option(head, "HOST");
    if (dst == NULL) {
        return -1;
    }
    char ip[20] = {0};
    int cnt = 0;
    while (*dst != '\n') {
        if (*dst == ' ')
            continue;
        if (*dst == ':')
            break;
        ip[cnt++] = *dst++;
    }
    uint16_t port = atoi(++dst);
    to_sin->sin_port = htons(port);
    to_sin->sin_addr.s_addr = inet_addr(ip);
    
    cnt = HTTPMU_MAX_RETRIES;
    while (cnt--) {
        int ret = sendto(socket, buff, len, 0, &to, sizeof(struct sockaddr));
        if (ret < 0)
           printf("ERROR: sendto %s\n", ip);
        httpmu_delay_sec(rand() % HTTPMU_MAX_RETRY_INTERVAL);
    }
    return 0;
}
            
httpmu_s_t *httpmu_create_server(const char *ip, const uint16_t port)
{
    httpmu_s_t *server = (httpmu_s_t *)httpmu_malloc(sizeof(httpmu_s_t));
    if (server == NULL)
        return NULL;
    memset(server, 0, sizeof(httpmu_s_t));
    server->socket = httpmu_create_socket(ip, port);
    if (server->socket < 0) {
        httpmu_free(server);
        return NULL;
    }
    httpmu_mutex_init(&server->request_mutex);
    return server;
}
            
void httpmu_free_server(httpmu_s_t *server)
{
    httpmu_stop_server(server);
    
    httpmu_close_socket(server->socket);
    
    for (int i = 0; i < HTTPMU_MAX_RESOURCE; i++) {
        if (server->request[i].handle) {
            httpmu_free(server->request[i].method);
        }
    }
    httpmu_mutex_free(&server->request_mutex);
    httpmu_free(server);
}

static http_head_t *get_request_head(char *buff, uint16_t len)
{
    int cnt = 0;
    int flag = 0;
    // get method
    const char *method = buff;
    flag = 0;
    while (cnt < len) {
        if (buff[cnt] == ' ') {
            buff[cnt] = 0;
            cnt++;
            flag = 1;
            break;
        }
        cnt++;
    }
    if (!flag) {
        return NULL;
    }
    // get url
    const char *url = buff + cnt;
    flag = 0;
    while (cnt < len) {
        if (buff[cnt] == ' ') {
            buff[cnt] = 0;
            cnt++;
            flag = 1;
            break;
        }
        cnt++;
    }
    if (!flag) {
        return NULL;
    }
    // skip string: HTTP/1.1\r\n
    flag = 0;
    while (cnt < len) {
        if (buff[cnt - 1] == '\r' && buff[cnt] == '\n') {
            cnt++;
            flag = 1;
            break;
        }
        cnt++;
    }
    if (!flag) {
        return NULL;
    }
    http_head_t *head = httpmu_create_head(method, url);
    if (head == NULL)
        return NULL;
    // parse options
    int offset = 0;
    buff += cnt;
    while (cnt < len) {
        int offset = 0;
        const char *key = buff;
        const char *val;
        flag = 0;
        while (cnt + offset < len) {
            if (buff[offset] == ':') {
                buff[offset] = 0;
                val = buff + offset + 1;
                offset++;
                flag = 1;
                break;
            }
            offset++;
        }
        if (!flag) {
            break;
        }
        // remove space charecate
        while (cnt + offset < len) {
            if (buff[offset] != ' ') {
                break;
            }
            offset++;
        }
        val = buff + offset;
        flag = 0;
        while (cnt + offset < len) {
            if (buff[offset] == '\n' && buff[offset - 1] == '\r') {
                buff[offset - 1] = 0;
                offset++;
                flag = 1;
                break;
            }
            offset++;
        }
        if (!flag) {
            break;
        }
        httpmu_set_option(head, key, val);
        // check if end of request head
        if (buff[offset] == '\r' && buff[offset + 1] == '\n') {
            break;
        }
        cnt += offset;
        buff += offset;
    }
    return head;
}
            
static const char *get_request_content(char *buff, uint16_t len)
{
    const char *content = NULL;
    int cnt = 4;
    while (cnt < len) {
        if (buff[cnt - 4] == '\r' && buff[cnt - 3] == '\n' &&
            buff[cnt - 2] == '\r' && buff[cnt - 1] == '\n') {
                content = buff + cnt;
                break;
        }
        cnt++;
    }
    return content;
}

static void* server_thread(void *arg)
{
    httpmu_s_t *server = (httpmu_s_t *)arg;
    server->is_stop = 0;
    int socket = server->socket;
    while (!server->is_stop) {
        //struct sockaddr from;
        //struct sockaddr_in *from_sin = (struct sockaddr_in*) &from;
        //socklen_t fromLen = sizeof(from);
        char buff[1500] = {0};
        int len;
        if((len = recv(socket, &buff, sizeof(buff), 0)) >= 0) {
            //uint8_t *ip = (uint8_t *) &from_sin->sin_addr.s_addr;
            //uint16_t from_port = ntohs(from_sin->sin_port);
            //printf("recvfrom - %d bytes from %d.%d.%d.%d:%d\n", packetLen, ip[0], ip[1], ip[2], ip[3], from_port);
            http_head_t *head = get_request_head(buff, len);
            if (head) {
                httpmu_mutex_lock(&server->request_mutex);
                for (int i = 0; i < HTTPMU_MAX_RESOURCE; i++) {
                    if (server->request[i].handle){
                    if (strcasecmp(head->method, server->request[i].method) == 0) {
                        const char *content = get_request_content(buff, len);
                        server->request[i].handle(head, content, server->request[i].priv_data);
                        break;
                    }
                    }
                }
                httpmu_mutex_unlock(&server->request_mutex);
                httpmu_free_head(head);
            }
        }
    }
    return NULL;
}

int httpmu_start_server(httpmu_s_t *server)
{
    httpmu_mutex_init(&server->request_mutex);
    int ret = httpmu_start_thread(&server->server_id, server_thread, server);
    if (ret < 0)
        return ret;
    return ret;
}

int httpmu_stop_server(httpmu_s_t *server)
{
    if (!server->is_stop) {
        server->is_stop = 1;
        httpmu_stop_thread(server->server_id);
        httpmu_mutex_free(&server->request_mutex);
    }
    return 0;
}

int httpmu_server_set_resource(httpmu_s_t *server, request_handle_t handle, const char *method, void *data)
{
    int ret = -1;
    httpmu_mutex_lock(&server->request_mutex);
    for (int i = 0; i < HTTPMU_MAX_RESOURCE; i++) {
        if (server->request[i].handle == NULL) {
            int len = strlen(method) + 1;
            memcpy(server->request[i].method, method, len);
            server->request[i].priv_data = data;
            server->request[i].handle = handle;
            ret = 0;
            printf("set resource: %s  %s\n", method, server->request[i].method);
            break;
        }
    }
    httpmu_mutex_unlock(&server->request_mutex);
    return ret;
}

int httpmu_server_del_resource(httpmu_s_t *server, request_handle_t handle)
{
    httpmu_mutex_lock(&server->request_mutex);
    for (int i = 0; i < HTTPMU_MAX_RESOURCE; i++) {
        if (server->request[i].handle == handle) {
            server->request[i].handle = NULL;
            memset(server->request[i].method, 0, sizeof(server->request[i].method));
            server->request[i].priv_data = NULL;
            break;
        }
    }
    httpmu_mutex_unlock(&server->request_mutex);
    return 0;
}
